﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SurvivalStrategy.Modules;

namespace SurvivalStrategy
{
    class ActionManager
    { 
        private Dictionary<ActionTags, List<ActionModuleFunction>> actions;     //zbior wszystkich dostepnych akcji
        private Dictionary<ActionTags, PriorityQueue> requestsQueues;           //kolejka z requestami, zalezna od tagu
        private Dictionary<ActionTags, ActionModuleFunction> activeActions;     //aktywne akcje, po jednej dla kazdego tagu
        private Dictionary<int, List<Request>> expirationList;                  //lista zarzadzajaca czasem oczekiwania requestu
      
        private ActionTags exclusiveAction;

        private int expirationTimer;                                            //timer liczacy czas do zresetowania kluczy w expirationList
        private const int TIMER_RESET = 1000;                                   //czas po ktorym resetuje klucze w expirationList

        private int id;

        public ActionManager(int id)
        {
            this.id = id;

            actions = new Dictionary<ActionTags, List<ActionModuleFunction>>();
            requestsQueues = new Dictionary<ActionTags, PriorityQueue>();
            activeActions = new Dictionary<ActionTags, ActionModuleFunction>();
            expirationList = new Dictionary<int, List<Request>>();
            expirationTimer = 0;
            exclusiveAction = ActionTags.Null;
        }

        public void Update()
        {
            ManageRequests();                   //zarzadza requestami
            DoActions();                        //wykonuje aktywne akcje
            ManageReqestsExpirations();         //usuwa przedawnione requesty
        }

        public void AddRequest(Request request) //dodaje request do odpowieniej kolejki, w zaleznosci od tagu 
        {                                       //dodaje requesta do listy usuwajacej przeterminowane requesty
            if (requestsQueues.ContainsKey(request.Tag))
                requestsQueues[request.Tag].Enqueue(request);
            else
            {
                requestsQueues.Add(request.Tag, new PriorityQueue());
                requestsQueues[request.Tag].Enqueue(request);
            }

            int lifeTime = request.LifeSpan + expirationTimer;
            if (expirationList.ContainsKey(lifeTime))
                expirationList[lifeTime].Add(request);
            else
            {
                expirationList.Add(lifeTime, new List<Request>());
                expirationList[lifeTime].Add(request);    //ustawia czas usuniecia request u na aktualny czas + zywotnosc requesta
            }
        }

        public void ManageRequests()        //obsluguje kolejki wszysstkich requestow, jezeli nie ma akywnej akcji o danym tagu
        {                                   //albo aktywna akcja ma mniejszy priorytet to wtedy aktywuje nowa
            List<ActionTags> tagsToRemove = new List<ActionTags>();
            foreach(ActionTags tag in requestsQueues.Keys)
            {
                if (activeActions.ContainsKey(tag))
                {
                    if (requestsQueues[tag].Top().Priority > activeActions[tag].Priority)
                    {
                        ActivateRequest(requestsQueues[tag].Dequeue());
                    }
                }
                else
                {
                    activeActions.Add(tag, null);
                    ActivateRequest(requestsQueues[tag].Dequeue());
                }
                if (requestsQueues[tag].Count == 0)
                    tagsToRemove.Add(tag);
            }
            foreach (ActionTags tag in tagsToRemove)
                requestsQueues.Remove(tag);
        }

        private void ActivateRequest(Request r) //interpretuje requesta na konkretna akcje i ustawia ja jako aktywna
        {
            activeActions[r.Tag] = GetBestAction(r.Tag);                //wybiera najlepsza akcje dla danego zadania
            activeActions[r.Tag].SetArgs(r.Args);                       //przekazuje argumenty
            activeActions[r.Tag].SetPriority(r.Priority);               //i ustawia priorytet


            int key = -1;
            foreach (List<Request> req in expirationList.Values)        //szuka podanego requesta w liscie przedawnien i usuwa go
            {
                if (req.Contains(r))
                {
                    key = expirationList.Keys.First<int>(o => expirationList[o] == req);
                    expirationList[key].Remove(r);
                    break;
                }
            }
            if (key != -1)
                if( expirationList[key].Count == 0)
                    expirationList.Remove(key);
        }

        private void DoActions()                //wykonuje aktywne akcje
        {
            List<ActionTags> tagsToRemove = new List<ActionTags>();
            foreach (ActionTags tag in activeActions.Keys)
            {
                if (exclusiveAction == ActionTags.Null || exclusiveAction == tag)
                    if (activeActions[tag].ActionModuleFunc(activeActions[tag].Args))
                        tagsToRemove.Add(tag);
                    else
                    {
                        if (activeActions[tag].SetExclusiveFlag)
                            exclusiveAction = tag;
                        else if (exclusiveAction == tag) exclusiveAction = ActionTags.Null;
                    }
            }
            foreach(ActionTags tag in tagsToRemove)
                activeActions.Remove(tag);
        }

        private void ManageReqestsExpirations()//obsluga listy usuwania przedawnionych akcji
        {
            if (expirationList.ContainsKey(expirationTimer))
            {
                foreach (Request request in expirationList[expirationTimer])
                {
                    requestsQueues[request.Tag].Remove(request); // trzeba cos wymyslec zeby ten sort po removie sie za kazdym razem nie wywolywal
                    if (requestsQueues[request.Tag].Count == 0)
                        requestsQueues.Remove(request.Tag);
                }
                expirationList.Remove(expirationTimer);
            }
            if (expirationTimer >= TIMER_RESET)
                RequestExpirationsTimer();

            expirationTimer++;
        }

        private void RequestExpirationsTimer()                           //Resetuje klucze expiration list, zmiejszajac je o wartosc expirationTimer
        {
            Dictionary<int, List<Request>> tempList = expirationList;
            expirationList = new Dictionary<int, List<Request>>();
            foreach (int key in tempList.Keys)
            {
                int newKey = key - expirationTimer;
                expirationList.Add(newKey, tempList[key]);
            }
            expirationTimer = 0;
        }

        private ActionModuleFunction GetBestAction(ActionTags tag)       //wybiera najlepsza akcje o podanym tagu
        {
            ActionModuleFunction result = null;
            int resultPriority = int.MinValue;

            foreach (ActionModuleFunction func in actions[tag])
            {
                int priority = func.ActionModulePriority();
                if (resultPriority < priority)
                {
                    result = func;
                    resultPriority = priority;
                }
            }
            return result;
        }

        public void AddModule(ActionModule module)                      //dodaje wszystkie akcje z modulu, pod odpowiednie tagi
        {
            module.SetID(this.id);

            foreach (ActionModuleFunction func in module.ModuleFunctions)
            {
                if (actions.ContainsKey(func.Tag))
                    actions[func.Tag].Add(func);
                else
                {
                    actions.Add(func.Tag, new List<ActionModuleFunction>());
                    actions[func.Tag].Add(func);
                }
            }
        }

        public void RemoveModule(ActionModule module)                   //usuwa wszystkie akcje danego modulu
        {
            foreach (ActionModuleFunction func in module.ModuleFunctions)
            {
                actions[func.Tag].Remove(func);
                if (actions[func.Tag].Count == 0)
                    actions.Remove(func.Tag);
            }
        }
    }
}
